﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BreakOutBattles.Game.Physics;
using BreakOutBattles.Sound;
#endregion

namespace BreakOutBattles.Game.Items
{
    public class Ball : GameItem, ISolidObject
    {
        #region Delegates and Events
        public delegate void OutOfBoundsEventHandler(object sender, object args);
        public event OutOfBoundsEventHandler OutOfBoundsEvent;

        public delegate void GameItemCollisionEventHandler(object sender, object args);
        public event GameItemCollisionEventHandler GameItemCollisionEvent;
        #endregion

        #region Fields
        private const float DEFAULT_X_SPEED = 100;
        private const float MAX_X_SPEED = 300;
        private const float DEFAULT_Y_SPEED = 300;
        private const float MAX_Y_SPEED = 400;
        private const float SPEED_UP_FACTOR = 0.2f;
        private Vector2 VERTICAL_INVERSION = new Vector2(1, -1);
        private Vector2 HORIZONTAL_INVERSION = new Vector2(-1, 1);
        
        private Random random;
        private Rectangle gameBounds;
        private Vector2 direction = Vector2.Zero;
        private Vector2 speed;
        private float timeOnAir = 0;

        #endregion

        public Ball(Rectangle gameBounds, Texture2D itemTexture)
            : base(Vector2.Zero, itemTexture)
        {
            this.gameBounds = gameBounds;

            random = new Random();
            speed = new Vector2(DEFAULT_X_SPEED, DEFAULT_Y_SPEED);
            currentLocation = new Vector2(gameBounds.Center.X, gameBounds.Bottom - 200);
            initialLocation = currentLocation;

            Reset();
        }

        /// <summary>
        /// Resets the ball to its default behaivour.
        /// </summary>
        public override void Reset()
        {
            base.Reset();

            timeOnAir = 0;
            speed.X = DEFAULT_X_SPEED;
            speed.Y = DEFAULT_Y_SPEED;
            direction = VERTICAL_INVERSION;
        }

        #region Collision Detection
        /// <summary>
        /// Checks if the ball is bellow the bottom of the current game screen bounds.
        /// </summary>
        /// <returns></returns>
        private bool IsOutOfBounds()
        {
            return CollisionRect.Bottom > gameBounds.Bottom;
        }

        /// <summary>
        /// Affects the speed of the ball, depending on the type of the solid object colliding with the ball.
        /// </summary>
        /// <param name="solidObject">The solid object that is colliding with the ball.</param>
        private void ChangeSpeed(ISolidObject solidObject)
        {
            if (solidObject is Paddle)
            {
                Rectangle rightRect = new Rectangle(solidObject.CollisionRect.Right - solidObject.CollisionRect.Width / 4,
                                                    solidObject.CollisionRect.Y,
                                                    solidObject.CollisionRect.Width / 4, 
                                                    solidObject.CollisionRect.Height);

                Rectangle leftRect = new Rectangle(solidObject.CollisionRect.Left, 
                                                   solidObject.CollisionRect.Y,
                                                   solidObject.CollisionRect.Width / 4, 
                                                   solidObject.CollisionRect.Height);

                if ((CollisionRect.Center.X > rightRect.X) || (CollisionRect.X < (leftRect.X + leftRect.Width)))
                {
                    speed.X = DEFAULT_X_SPEED * 2;
                }
                else
                {
                    speed.X = DEFAULT_X_SPEED;
                    speed.Y = DEFAULT_Y_SPEED;
                }

                //InvertHorizontalDirection(solidObject);
            }
            else
            {
                speed.Y++;
            }

            if (solidObject.OnMotion)
            {
                speed.X = MathHelper.Clamp(speed.X + speed.X * SPEED_UP_FACTOR, DEFAULT_X_SPEED, MAX_X_SPEED);
                speed.Y = MathHelper.Clamp(speed.Y + speed.Y * SPEED_UP_FACTOR, DEFAULT_Y_SPEED, MAX_Y_SPEED);
            }
        }

        /// <summary>
        /// Inverts the horizontal direction of the ball. If the ball collides with some
        /// game items, the direction of the ball will be randomized. 
        /// </summary>
        /// <param name="lastCollisionObject">The last game object that collided with the ball.</param>
        private void InvertHorizontalDirection(ISolidObject lastCollisionObject)
        {
            direction *= HORIZONTAL_INVERSION;
        }

        /// <summary>
        /// Inverts the vertical direction of the ball. If the ball collides with some
        /// game items, the direction of the ball will be randomized. 
        /// </summary>
        /// <param name="lastCollisionObject">The last game object that collided with the ball.</param>
        private void InvertVerticalDirection(ISolidObject lastCollisionObject)
        {
            direction *= VERTICAL_INVERSION;

        }

        /// <summary>
        /// Stops the "on air" time counter when the ball hits a paddle.
        /// </summary>
        /// <param name="solidObject">A solid object to check if it is a paddle.</param>
        private void StopOnAirCounter(ISolidObject solidObject)
        {
            if (solidObject is Paddle)
            {
                timeOnAir = 0;
            }
        }

        /// <summary>
        /// Checks for collisions against other solid game object.
        /// </summary>
        /// <param name="solidObject">The object to check for collisions.</param>
        public void CollisionCheck(ISolidObject solidObject)
        {
            //Collision check with the Right and Left borders of a game item
            if (solidObject.CollisionRect.Contains(CollisionRect.Left, CollisionRect.Center.Y))
            {
                currentLocation.X = solidObject.CollisionRect.Right + 2;
                InvertHorizontalDirection(solidObject);
                ChangeSpeed(solidObject);
                StopOnAirCounter(solidObject);
                solidObject.RegisterCollision();
            }
            if(solidObject.CollisionRect.Contains(CollisionRect.Right, CollisionRect.Center.Y))
            {
                currentLocation.X = solidObject.CollisionRect.Left - CollisionRect.Width - 2;
                InvertHorizontalDirection(solidObject);
                ChangeSpeed(solidObject);
                StopOnAirCounter(solidObject);
                solidObject.RegisterCollision();
            }

            //Collision check with the Top and Bottom borders of a solid game item
            if (solidObject.CollisionRect.Contains(CollisionRect.Center.X, CollisionRect.Bottom))
            {
                currentLocation.Y = solidObject.CollisionRect.Top - CollisionRect.Height - 2;
                InvertVerticalDirection(solidObject);
                ChangeSpeed(solidObject);
                StopOnAirCounter(solidObject);
                solidObject.RegisterCollision();
            }
            if(solidObject.CollisionRect.Contains(CollisionRect.Center.X, CollisionRect.Top))
            {
                currentLocation.Y = solidObject.CollisionRect.Bottom + 2;
                InvertVerticalDirection(solidObject);
                ChangeSpeed(solidObject);
                StopOnAirCounter(solidObject);
                solidObject.RegisterCollision();
            }
        }
        #endregion

        #region Event Handling
        /// <summary>
        /// Fires a collision event indicating that the ball collided
        /// with another gameItem.
        /// </summary>
        /// <param name="gameItem">The game item that collided with the ball.</param>
        private void FireGameItemCollisionEvent(GameItem gameItem)
        {
            if (GameItemCollisionEvent != null)
            {
                GameItemCollisionEvent(this, gameItem);
            }
        }

        /// <summary>
        /// Fires a collision event indicating that the ball is outside the
        /// playable area.
        /// </summary>
        private void FireOutOfBoundsEvent()
        {
            if (OutOfBoundsEvent != null)
            {
                OutOfBoundsEvent(this, null);
            }
        }
        #endregion

        /// <summary>
        /// Moves the ball at the current direction.
        /// </summary>
        private void MoveBall(GameTime gameTime)
        {
            timeOnAir += gameTime.ElapsedGameTime.Milliseconds;
            currentLocation += direction * (speed * (float)gameTime.ElapsedGameTime.TotalSeconds);

            if (IsOutOfBounds())
            {
                SoundManager.PlayEffect(SoundEffects.GAME_OVER);
                FireOutOfBoundsEvent();
                Reset();
            }
        }

        /// <summary>
        /// Updates the ball.
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            MoveBall(gameTime);
        }

        #region ISolidObject Members
        /// <summary>
        /// Gets the collision rectangle of this ball.
        /// </summary>
        public Rectangle CollisionRect
        {
            get
            {
                collisionRect.X = (int)currentLocation.X;
                collisionRect.Y = (int)currentLocation.Y;

                return collisionRect;
            }
            set
            {
                collisionRect = value;
            }
        }

        public void RegisterCollision()
        { }

        public bool OnMotion
        {
            get { return true; }
        }
        #endregion
    }
}
